/*******************************************************************************
 *  Floppy Dsik Controller for BBK/YuXing
 *
 *  Author:  fanoble <87430545@qq.com>
 *
 *  Create:   Nov 6, 2022, by fanoble
 *******************************************************************************
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "fdc.h"

static const FDC::FDC_CMD_DESC FdcCmdTable[32] =
{
	/* 0x00 */ { 1, 1, FDC::FdcNop },
	/* 0x01 */ { 1, 1, FDC::FdcNop },
	/* 0x02 */ { 9, 7, FDC::FdcReadTrack },
	/* 0x03 */ { 3, 0, FDC::FdcSpecify },
	/* 0x04 */ { 2, 1, FDC::FdcSenseDriveStatus },
	/* 0x05 */ { 9, 7, FDC::FdcWriteData },
	/* 0x06 */ { 9, 7, FDC::FdcReadData },
	/* 0x07 */ { 2, 0, FDC::FdcRecalibrate },
	/* 0x08 */ { 1, 2, FDC::FdcSenseIntStatus },
	/* 0x09 */ { 9, 7, FDC::FdcWriteDeletedData },
	/* 0x0A */ { 2, 7, FDC::FdcReadID },
	/* 0x0B */ { 1, 1, FDC::FdcNop },
	/* 0x0C */ { 9, 7, FDC::FdcReadDeletedData },
	/* 0x0D */ { 6, 7, FDC::FdcFormatTrack },
	/* 0x0E */ { 1, 1, FDC::FdcNop },
	/* 0x0F */ { 3, 0, FDC::FdcSeek },
	/* 0x10 */ { 1, 1, FDC::FdcNop },
	/* 0x11 */ { 9, 7, FDC::FdcScanEqual },
	/* 0x12 */ { 1, 1, FDC::FdcNop },
	/* 0x13 */ { 1, 1, FDC::FdcNop },
	/* 0x14 */ { 1, 1, FDC::FdcNop },
	/* 0x15 */ { 1, 1, FDC::FdcNop },
	/* 0x16 */ { 1, 1, FDC::FdcNop },
	/* 0x17 */ { 1, 1, FDC::FdcNop },
	/* 0x18 */ { 1, 1, FDC::FdcNop },
	/* 0x19 */ { 9, 7, FDC::FdcScanLowOrEqual },
	/* 0x1A */ { 1, 1, FDC::FdcNop },
	/* 0x1B */ { 1, 1, FDC::FdcNop },
	/* 0x1C */ { 1, 1, FDC::FdcNop },
	/* 0x1D */ { 9, 7, FDC::FdcScanHighOrEqual },
	/* 0x1E */ { 1, 1, FDC::FdcNop },
	/* 0x1F */ { 1, 1, FDC::FdcNop },
};

FDC::FDC()
{
	bFdcIrq = 0;
	bFdcHwReset = 0;
	bFdcSoftReset = 0;

	bFdcDmaInt = 0;
	nFdcDrvSel = 0;
	nFdcMotor = 0;
	nFdcMainStatus = FDC_MS_RQM;

	nFDCStatus[0] = 0;
	nFDCStatus[1] = 0;
	nFDCStatus[2] = 0;
	nFDCStatus[3] = 0;

	bFdcCycle = 0;
	bFdcPhase = FDC_PH_IDLE;

	nFdcCylinder = 0;

	pFdcDataPtr = 0;

	bDirty = 0;
	nDiskSize = 0;
	pDiskImage = 0;
}

FDC::~FDC()
{
	if (pDiskImage) free(pDiskImage);
}

//==============================================================================

int FDC::LoadDiskImage(const char* fname)
{
	FILE* fp;

	// try to load disk image
	if (!(fp = ::fopen(fname, "rb"))) {
		return 0;
	}

	::fseek(fp, 0, SEEK_END);
	nDiskSize = ::ftell(fp);
	::fseek(fp, 0, SEEK_SET);

	if (pDiskImage) free(pDiskImage);

	if (!(pDiskImage = (unsigned char*)malloc(nDiskSize))) {
		fclose(fp);
		return 0;
	}

	::fread(pDiskImage, nDiskSize, 1, fp);
	fclose(fp);

	bDirty = 0;
	strcpy(szDiskName, fname);

	return 1;
}

int FDC::SaveDiskImage()
{
	FILE* fp;
	
	if (!(fp = ::fopen(szDiskName, "wb"))) {
		return 0;
	}
	
	if (!::fwrite(pDiskImage, nDiskSize, 1, fp)) {
		fclose(fp);
		return 0;
	}

	fclose(fp);
	
	bDirty = 0;

	return 1;
}

//==============================================================================

unsigned char FDC::Read(unsigned char nPort)
{
	unsigned char nData;

	switch (nPort) {
		case 0: // 3F0: FDCDMADackIO
			nData = *pFdcDataPtr++;
			break;
		case 1: // 3F1: FDCDMATcIO
			nData = *pFdcDataPtr++;
			break;
		case 2: // 3F2: FDCDRQPortI/FDCCtrlPortO
			// I: D6 : FDC DRQ
			nData = 0x40;
			break;
		case 3: // 3F3: FDCIRQPortI/FDCDMADackIO
			// I: D6 : IRQ
			if (bFdcIrq)
				nData = 0x40;
			else
				nData = 0;
			break;

		case 4: // 3F4: FDCResetPortO/FDCStatPortI
			// I: D7 : FDC ready
			// I: D6 : FDC dir
			// I: D5 : FDC busy
			nData = nFdcMainStatus;
			break;
		case 5: // 3F5: FDCDataPortIO
			nData = bFdcResults[bFdcCycle];
			bFdcCycle++;
			if (bFdcCycle == pFdcCmd->bRLength) {
				// prepare for next command
				bFdcCycle = 0;
				bFdcPhase = FDC_PH_IDLE;

				nFdcMainStatus &= ~FDC_MS_DATA_IN;
				nFdcMainStatus |= FDC_MS_RQM;
			}
			break;
		case 7: // 3F7: FDCChangePortI/FDCSpeedPortO
			// I: D7 : Disk changed
			nData = 0;
			break;
		default:
			nData = 0;
			break;
	}

	return nData;
}

void FDC::Write(unsigned char nPort, unsigned nData)
{
	switch (nPort) {
		case 0: // 3F0: FDCDMADackIO
			*pFdcDataPtr++ = nData;
			bDirty = 1;
			break;
		case 1: // 3F1: FDCDMATcIO
			*pFdcDataPtr++ = nData;
			bDirty = 1;
			break;
		case 2: // 3F2: FDCDRQPortI/FDCCtrlPortO
			// O: D5 : Drv B motor
			// O: D4 : Drv A motor
			// O: D3 : Enable INT and DMA
			// O: D2 : not FDC Reset
			// O: D[1:0] : Drv sel

			bFdcDmaInt = (nData & 8) ? 1 : 0;
			nFdcDrvSel = nData & 3;
			nFdcMotor = nData >> 4;

			if (nData & 4) {
				if (bFdcSoftReset) {
					FdcSoftReset();

					bFdcSoftReset = 0;

					// IRQ after soft reset
					if (0 == nFdcDrvSel) {
						// Driver A Only
						bFdcIrq = pDiskImage ? 1 : 0;
					} else {
						bFdcIrq = 0;
					}
				}
			} else {
				if (!bFdcSoftReset) {
					bFdcSoftReset = 1;
					bFdcIrq = 0;
				}
			}

			break;
		case 3: // 3F3: FDCIRQPortI/FDCDMADackIO
			// I: D6 : IRQ
			nData = nData;
			break;
		case 4: // 3F4: FDCResetPortO/FDCStatPortI
			// O: D6 : FDC pin reset
			if (nData & 0x40) {
				if (!bFdcHwReset) {
					bFdcHwReset = 1;
					bFdcIrq = 0;
				}
			} else {
				if (bFdcHwReset) {
					FdcHardReset();
					bFdcHwReset = 0;
				}
			}
			break;
		case 5: // 3F5: FDCDataPortIO
			switch (bFdcPhase) {
				case FDC_PH_EXECUTION:
				case FDC_PH_RESULT:
					// ERROR
					break;
				case FDC_PH_IDLE:
				default:
					bFdcCycle = 0;
					bFdcPhase = FDC_PH_COMMAND;
					pFdcCmd = &FdcCmdTable[nData & FDC_CC_MASK];
					// fall through
				case FDC_PH_COMMAND:
					bFdcCommands[bFdcCycle] = nData;
					bFdcCycle++;
					if (bFdcCycle == pFdcCmd->bWLength) {
						bFdcPhase = FDC_PH_EXECUTION;
//						nFdcMainStatus &= ~FDC_MS_RQM;

						pFdcCmd->pFun(this);

						// prepare for reading
						if (pFdcCmd->bRLength) {
							nFdcMainStatus |= FDC_MS_DATA_IN;
							bFdcPhase = FDC_PH_RESULT;
						} else {
							bFdcPhase = FDC_PH_IDLE;
						}

						bFdcCycle = 0;
					}
					break;
			}
			break;
		case 7: // 3F7: FDCChangePortI/FDCSpeedPortO
			// I: D7 : Disk changed
			// O: D[1:0] : 00 500kbps(1.2M, 1.44M)
			//             01 300kbps(360K)
			//             10 250kbps(720K)
			nData = nData;
			break;
		default:
			break;
	}
}

//==============================================================================

void FDC::FdcHardReset(void)
{
	bFdcDmaInt = 0;
	nFdcDrvSel = 0;
	nFdcMotor = 0;
	nFdcMainStatus = FDC_MS_RQM;

	nFDCStatus[0] = 0;
	nFDCStatus[1] = 0;
	nFDCStatus[2] = 0;
	nFDCStatus[3] = 0;

	bFdcCycle = 0;
	bFdcPhase = FDC_PH_IDLE;
}

void FDC::FdcSoftReset(void)
{
	nFdcDrvSel = 0;
	nFdcMotor = 0;
	nFdcMainStatus = FDC_MS_RQM;

	nFDCStatus[0] = 0;
	nFDCStatus[1] = 0;
	nFDCStatus[2] = 0;
	nFDCStatus[3] = 0;

	bFdcCycle = 0;
	bFdcPhase = FDC_PH_IDLE;
}

void FDC::FdcNop(FDC* thiz)
{
	thiz->nFDCStatus[0] = FDC_S0_IC1;
	thiz->bFdcResults[0] = thiz->nFDCStatus[0];
}

void FDC::FdcReadTrack(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcSpecify(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcSenseDriveStatus(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcWriteData(FDC* thiz)
{
	unsigned char C = thiz->bFdcCommands[2];
	unsigned char H = thiz->bFdcCommands[3];
	unsigned char R = thiz->bFdcCommands[4];
	unsigned char N = thiz->bFdcCommands[5];

	int LBA;

	LBA = H * 18 + C * 36 + (R - 1);

	thiz->pFdcDataPtr = thiz->pDiskImage + LBA * 512;

	R++;
	if (19 == R) {
		R = 1;
		H++;
		if (2 == H) {
			C++;
			if (80 == C)
				C = 0;
		}
	}

	thiz->nFDCStatus[0] = 0;

	thiz->bFdcResults[0] = thiz->nFDCStatus[0]; // ST0
	thiz->bFdcResults[1] = thiz->nFDCStatus[1]; // ST1
	thiz->bFdcResults[2] = thiz->nFDCStatus[2]; // ST2
	thiz->bFdcResults[3] = C;
	thiz->bFdcResults[4] = H;
	thiz->bFdcResults[5] = R;
	thiz->bFdcResults[6] = N;
}

void FDC::FdcReadData(FDC* thiz)
{
	unsigned char C = thiz->bFdcCommands[2];
	unsigned char H = thiz->bFdcCommands[3];
	unsigned char R = thiz->bFdcCommands[4];
	unsigned char N = thiz->bFdcCommands[5];

	int LBA;

	LBA = H * 18 + C * 36 + (R - 1);

	thiz->pFdcDataPtr = thiz->pDiskImage + LBA * 512;

	R++;
	if (19 == R) {
		R = 1;
		H++;
		if (2 == H) {
			C++;
			if (80 == C)
				C = 0;
		}
	}

	thiz->nFDCStatus[0] = 0;

	thiz->bFdcResults[0] = thiz->nFDCStatus[0]; // ST0
	thiz->bFdcResults[1] = thiz->nFDCStatus[1]; // ST1
	thiz->bFdcResults[2] = thiz->nFDCStatus[2]; // ST2
	thiz->bFdcResults[3] = C;
	thiz->bFdcResults[4] = H;
	thiz->bFdcResults[5] = R;
	thiz->bFdcResults[6] = N;
}

void FDC::FdcRecalibrate(FDC* thiz)
{
	unsigned char US;

	US = thiz->bFdcCommands[1] & 3;

	thiz->nFDCStatus[0] = US ? (FDC_S0_SE | FDC_S0_IC0) : FDC_S0_SE;
}

void FDC::FdcSenseIntStatus(FDC* thiz)
{
	if (0 == thiz->nFdcDrvSel)	// Drv A Only
		thiz->nFDCStatus[0] = FDC_S0_IC0 | FDC_S0_IC1;
	else
		thiz->nFDCStatus[0] = FDC_S0_IC0 | FDC_S0_SE;

	thiz->bFdcResults[0] = thiz->nFDCStatus[0];
	thiz->bFdcResults[1] = thiz->nFdcCylinder;	// PCN
}

void FDC::FdcWriteDeletedData(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcReadID(FDC* thiz)
{
	thiz->nFDCStatus[0] = 0;

	thiz->bFdcResults[0] = thiz->nFDCStatus[0];
}

void FDC::FdcReadDeletedData(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcFormatTrack(FDC* thiz)
{
	unsigned char C = thiz->bFdcCommands[1];
	unsigned char H = thiz->bFdcCommands[2];
	unsigned char R = thiz->bFdcCommands[3];
	unsigned char N = thiz->bFdcCommands[4];

	int LBA;

	LBA = H * 18 + C * 36 + (R - 1);

	thiz->pFdcDataPtr = thiz->pDiskImage + LBA * 512;

	R++;
	if (19 == R) {
		R = 1;
		H++;
		if (2 == H) {
			C++;
			if (80 == C)
				C = 0;
		}
	}

	thiz->nFDCStatus[0] = 0;

	thiz->bFdcResults[0] = thiz->nFDCStatus[0]; // ST0
	thiz->bFdcResults[1] = thiz->nFDCStatus[1]; // ST1
	thiz->bFdcResults[2] = thiz->nFDCStatus[2]; // ST2
	thiz->bFdcResults[3] = C;
	thiz->bFdcResults[4] = H;
	thiz->bFdcResults[5] = R;
	thiz->bFdcResults[6] = N;
}

void FDC::FdcSeek(FDC* thiz)
{
	// new cylinder number
	unsigned char NCN;
	unsigned char US;

	US = thiz->bFdcCommands[1] & 3;
	NCN = thiz->bFdcCommands[2];

	thiz->nFdcCylinder = NCN;

	thiz->nFDCStatus[0] = FDC_S0_SE;
}

void FDC::FdcScanEqual(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcScanLowOrEqual(FDC* thiz)
{
	thiz = thiz;
}

void FDC::FdcScanHighOrEqual(FDC* thiz)
{
	thiz = thiz;
}

/*******************************************************************************
                           E N D  O F  F I L E
*******************************************************************************/
